// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "cc/resources/video_resource_updater.h"

#include <stddef.h>
#include <stdint.h>

#include "base/bind.h"
#include "base/memory/ptr_util.h"
#include "cc/resources/resource_provider.h"
#include "cc/test/fake_output_surface.h"
#include "cc/test/fake_output_surface_client.h"
#include "cc/test/fake_resource_provider.h"
#include "cc/test/test_shared_bitmap_manager.h"
#include "cc/test/test_web_graphics_context_3d.h"
#include "cc/trees/blocking_task_runner.h"
#include "gpu/GLES2/gl2extchromium.h"
#include "media/base/video_frame.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace cc {
namespace {

    class WebGraphicsContext3DUploadCounter : public TestWebGraphicsContext3D {
    public:
        void texSubImage2D(GLenum target,
            GLint level,
            GLint xoffset,
            GLint yoffset,
            GLsizei width,
            GLsizei height,
            GLenum format,
            GLenum type,
            const void* pixels) override
        {
            ++upload_count_;
        }

        void texStorage2DEXT(GLenum target,
            GLint levels,
            GLuint internalformat,
            GLint width,
            GLint height) override
        {
            immutable_texture_created_ = true;
        }

        GLuint createTexture() override
        {
            ++created_texture_count_;
            return TestWebGraphicsContext3D::createTexture();
        }

        void deleteTexture(GLuint texture) override
        {
            --created_texture_count_;
            TestWebGraphicsContext3D::deleteTexture(texture);
        }

        int UploadCount() { return upload_count_; }
        void ResetUploadCount() { upload_count_ = 0; }

        int TextureCreationCount() { return created_texture_count_; }
        void ResetTextureCreationCount() { created_texture_count_ = 0; }

        bool WasImmutableTextureCreated() { return immutable_texture_created_; }
        void ResetImmutableTextureCreated() { immutable_texture_created_ = false; }

    private:
        int upload_count_;
        int created_texture_count_;
        bool immutable_texture_created_;
    };

    class SharedBitmapManagerAllocationCounter : public TestSharedBitmapManager {
    public:
        std::unique_ptr<SharedBitmap> AllocateSharedBitmap(
            const gfx::Size& size) override
        {
            ++allocation_count_;
            return TestSharedBitmapManager::AllocateSharedBitmap(size);
        }

        int AllocationCount() { return allocation_count_; }
        void ResetAllocationCount() { allocation_count_ = 0; }

    private:
        int allocation_count_;
    };

    class VideoResourceUpdaterTest : public testing::Test {
    protected:
        VideoResourceUpdaterTest()
        {
            std::unique_ptr<WebGraphicsContext3DUploadCounter> context3d(
                new WebGraphicsContext3DUploadCounter());

            context3d_ = context3d.get();
            context3d_->set_support_texture_storage(true);

            context_provider_ = TestContextProvider::Create(std::move(context3d));
            context_provider_->BindToCurrentThread();
        }

        void SetUp() override
        {
            testing::Test::SetUp();

            shared_bitmap_manager_.reset(new SharedBitmapManagerAllocationCounter());
            resource_provider3d_ = FakeResourceProvider::Create(
                context_provider_.get(), shared_bitmap_manager_.get());
            resource_provider_software_ = FakeResourceProvider::Create(nullptr, shared_bitmap_manager_.get());
        }

        scoped_refptr<media::VideoFrame> CreateTestYUVVideoFrame()
        {
            const int kDimension = 10;
            gfx::Size size(kDimension, kDimension);
            static uint8_t y_data[kDimension * kDimension] = { 0 };
            static uint8_t u_data[kDimension * kDimension / 2] = { 0 };
            static uint8_t v_data[kDimension * kDimension / 2] = { 0 };

            scoped_refptr<media::VideoFrame> video_frame = media::VideoFrame::WrapExternalYuvData(
                media::PIXEL_FORMAT_YV16, // format
                size, // coded_size
                gfx::Rect(size), // visible_rect
                size, // natural_size
                size.width(), // y_stride
                size.width() / 2, // u_stride
                size.width() / 2, // v_stride
                y_data, // y_data
                u_data, // u_data
                v_data, // v_data
                base::TimeDelta()); // timestamp
            EXPECT_TRUE(video_frame);
            return video_frame;
        }

        scoped_refptr<media::VideoFrame> CreateWonkyTestYUVVideoFrame()
        {
            const int kDimension = 10;
            const int kYWidth = kDimension + 5;
            const int kUWidth = (kYWidth + 1) / 2 + 200;
            const int kVWidth = (kYWidth + 1) / 2 + 1;
            static uint8_t y_data[kYWidth * kDimension] = { 0 };
            static uint8_t u_data[kUWidth * kDimension] = { 0 };
            static uint8_t v_data[kVWidth * kDimension] = { 0 };

            scoped_refptr<media::VideoFrame> video_frame = media::VideoFrame::WrapExternalYuvData(
                media::PIXEL_FORMAT_YV16, // format
                gfx::Size(kYWidth, kDimension), // coded_size
                gfx::Rect(2, 0, kDimension, kDimension), // visible_rect
                gfx::Size(kDimension, kDimension), // natural_size
                -kYWidth, // y_stride (negative)
                kUWidth, // u_stride
                kVWidth, // v_stride
                y_data + kYWidth * (kDimension - 1), // y_data
                u_data, // u_data
                v_data, // v_data
                base::TimeDelta()); // timestamp
            EXPECT_TRUE(video_frame);
            return video_frame;
        }

        scoped_refptr<media::VideoFrame> CreateTestHighBitFrame()
        {
            const int kDimension = 10;
            gfx::Size size(kDimension, kDimension);

            scoped_refptr<media::VideoFrame> video_frame(media::VideoFrame::CreateFrame(
                media::PIXEL_FORMAT_YUV420P10, size, gfx::Rect(size), size,
                base::TimeDelta()));
            EXPECT_TRUE(video_frame);
            return video_frame;
        }

        static void ReleaseMailboxCB(const gpu::SyncToken& sync_token) { }

        scoped_refptr<media::VideoFrame> CreateTestHardwareVideoFrame(
            unsigned target)
        {
            const int kDimension = 10;
            gfx::Size size(kDimension, kDimension);

            gpu::Mailbox mailbox;
            mailbox.name[0] = 51;

            const gpu::SyncToken sync_token(
                gpu::CommandBufferNamespace::GPU_IO, 0,
                gpu::CommandBufferId::FromUnsafeValue(0x123), 7);
            gpu::MailboxHolder mailbox_holders[media::VideoFrame::kMaxPlanes] = {
                gpu::MailboxHolder(mailbox, sync_token, target)
            };
            scoped_refptr<media::VideoFrame> video_frame = media::VideoFrame::WrapNativeTextures(media::PIXEL_FORMAT_ARGB,
                mailbox_holders,
                base::Bind(&ReleaseMailboxCB),
                size, // coded_size
                gfx::Rect(size), // visible_rect
                size, // natural_size
                base::TimeDelta()); // timestamp
            EXPECT_TRUE(video_frame);
            return video_frame;
        }

        scoped_refptr<media::VideoFrame> CreateTestRGBAHardwareVideoFrame()
        {
            return CreateTestHardwareVideoFrame(GL_TEXTURE_2D);
        }

        scoped_refptr<media::VideoFrame> CreateTestStreamTextureHardwareVideoFrame(
            bool needs_copy)
        {
            scoped_refptr<media::VideoFrame> video_frame = CreateTestHardwareVideoFrame(GL_TEXTURE_EXTERNAL_OES);
            video_frame->metadata()->SetBoolean(
                media::VideoFrameMetadata::COPY_REQUIRED, needs_copy);
            return video_frame;
        }

        scoped_refptr<media::VideoFrame> CreateTestYuvHardwareVideoFrame()
        {
            const int kDimension = 10;
            gfx::Size size(kDimension, kDimension);

            const gpu::SyncToken sync_token(
                gpu::CommandBufferNamespace::GPU_IO, 0,
                gpu::CommandBufferId::FromUnsafeValue(0x123), 7);
            const unsigned target = GL_TEXTURE_RECTANGLE_ARB;
            const int kPlanesNum = 3;
            gpu::MailboxHolder mailbox_holders[media::VideoFrame::kMaxPlanes];
            for (int i = 0; i < kPlanesNum; ++i) {
                gpu::Mailbox mailbox;
                mailbox.name[0] = 50 + 1;
                mailbox_holders[i] = gpu::MailboxHolder(mailbox, sync_token, target);
            }
            scoped_refptr<media::VideoFrame> video_frame = media::VideoFrame::WrapNativeTextures(media::PIXEL_FORMAT_I420,
                mailbox_holders,
                base::Bind(&ReleaseMailboxCB),
                size, // coded_size
                gfx::Rect(size), // visible_rect
                size, // natural_size
                base::TimeDelta()); // timestamp
            EXPECT_TRUE(video_frame);
            return video_frame;
        }

        WebGraphicsContext3DUploadCounter* context3d_;
        scoped_refptr<TestContextProvider> context_provider_;
        std::unique_ptr<SharedBitmapManagerAllocationCounter> shared_bitmap_manager_;
        std::unique_ptr<ResourceProvider> resource_provider3d_;
        std::unique_ptr<ResourceProvider> resource_provider_software_;
    };

    TEST_F(VideoResourceUpdaterTest, SoftwareFrame)
    {
        VideoResourceUpdater updater(context_provider_.get(),
            resource_provider3d_.get());
        scoped_refptr<media::VideoFrame> video_frame = CreateTestYUVVideoFrame();

        VideoFrameExternalResources resources = updater.CreateExternalResourcesFromVideoFrame(video_frame);
        EXPECT_EQ(VideoFrameExternalResources::YUV_RESOURCE, resources.type);
    }

    TEST_F(VideoResourceUpdaterTest, HighBitFrameNoF16)
    {
        VideoResourceUpdater updater(context_provider_.get(),
            resource_provider3d_.get());
        scoped_refptr<media::VideoFrame> video_frame = CreateTestHighBitFrame();

        VideoFrameExternalResources resources = updater.CreateExternalResourcesFromVideoFrame(video_frame);
        EXPECT_EQ(VideoFrameExternalResources::YUV_RESOURCE, resources.type);
    }

    class VideoResourceUpdaterTestWithF16 : public VideoResourceUpdaterTest {
    public:
        VideoResourceUpdaterTestWithF16()
            : VideoResourceUpdaterTest()
        {
            context3d_->set_support_texture_half_float_linear(true);
        }
    };

    TEST_F(VideoResourceUpdaterTestWithF16, HighBitFrame)
    {
        VideoResourceUpdater updater(context_provider_.get(),
            resource_provider3d_.get());
        scoped_refptr<media::VideoFrame> video_frame = CreateTestHighBitFrame();

        VideoFrameExternalResources resources = updater.CreateExternalResourcesFromVideoFrame(video_frame);
        EXPECT_EQ(VideoFrameExternalResources::YUV_RESOURCE, resources.type);
        EXPECT_NEAR(resources.multiplier, 2.0, 0.1);
        EXPECT_NEAR(resources.offset, 0.5, 0.1);

        // Create the resource again, to test the path where the
        // resources are cached.
        VideoFrameExternalResources resources2 = updater.CreateExternalResourcesFromVideoFrame(video_frame);
        EXPECT_EQ(VideoFrameExternalResources::YUV_RESOURCE, resources2.type);
        EXPECT_NEAR(resources2.multiplier, 2.0, 0.1);
        EXPECT_NEAR(resources2.offset, 0.5, 0.1);
    }

    TEST_F(VideoResourceUpdaterTest, HighBitFrameSoftwareCompositor)
    {
        VideoResourceUpdater updater(nullptr, resource_provider_software_.get());
        scoped_refptr<media::VideoFrame> video_frame = CreateTestHighBitFrame();

        VideoFrameExternalResources resources = updater.CreateExternalResourcesFromVideoFrame(video_frame);
        EXPECT_EQ(VideoFrameExternalResources::SOFTWARE_RESOURCE, resources.type);
    }

    TEST_F(VideoResourceUpdaterTest, WonkySoftwareFrame)
    {
        VideoResourceUpdater updater(context_provider_.get(),
            resource_provider3d_.get());
        scoped_refptr<media::VideoFrame> video_frame = CreateWonkyTestYUVVideoFrame();

        VideoFrameExternalResources resources = updater.CreateExternalResourcesFromVideoFrame(video_frame);
        EXPECT_EQ(VideoFrameExternalResources::YUV_RESOURCE, resources.type);
    }

    TEST_F(VideoResourceUpdaterTest, WonkySoftwareFrameSoftwareCompositor)
    {
        VideoResourceUpdater updater(nullptr, resource_provider_software_.get());
        scoped_refptr<media::VideoFrame> video_frame = CreateWonkyTestYUVVideoFrame();

        VideoFrameExternalResources resources = updater.CreateExternalResourcesFromVideoFrame(video_frame);
        EXPECT_EQ(VideoFrameExternalResources::SOFTWARE_RESOURCE, resources.type);
    }

    TEST_F(VideoResourceUpdaterTest, ReuseResource)
    {
        VideoResourceUpdater updater(context_provider_.get(),
            resource_provider3d_.get());
        scoped_refptr<media::VideoFrame> video_frame = CreateTestYUVVideoFrame();
        video_frame->set_timestamp(base::TimeDelta::FromSeconds(1234));

        // Allocate the resources for a YUV video frame.
        context3d_->ResetUploadCount();
        VideoFrameExternalResources resources = updater.CreateExternalResourcesFromVideoFrame(video_frame);
        EXPECT_EQ(VideoFrameExternalResources::YUV_RESOURCE, resources.type);
        EXPECT_EQ(size_t(3), resources.mailboxes.size());
        EXPECT_EQ(size_t(3), resources.release_callbacks.size());
        EXPECT_EQ(size_t(0), resources.software_resources.size());
        // Expect exactly three texture uploads, one for each plane.
        EXPECT_EQ(3, context3d_->UploadCount());

        // Simulate the ResourceProvider releasing the resources back to the video
        // updater.
        for (ReleaseCallbackImpl& release_callback : resources.release_callbacks)
            release_callback.Run(gpu::SyncToken(), false, nullptr);

        // Allocate resources for the same frame.
        context3d_->ResetUploadCount();
        resources = updater.CreateExternalResourcesFromVideoFrame(video_frame);
        EXPECT_EQ(VideoFrameExternalResources::YUV_RESOURCE, resources.type);
        EXPECT_EQ(size_t(3), resources.mailboxes.size());
        EXPECT_EQ(size_t(3), resources.release_callbacks.size());
        // The data should be reused so expect no texture uploads.
        EXPECT_EQ(0, context3d_->UploadCount());
    }

    TEST_F(VideoResourceUpdaterTest, ReuseResourceNoDelete)
    {
        VideoResourceUpdater updater(context_provider_.get(),
            resource_provider3d_.get());
        scoped_refptr<media::VideoFrame> video_frame = CreateTestYUVVideoFrame();
        video_frame->set_timestamp(base::TimeDelta::FromSeconds(1234));

        // Allocate the resources for a YUV video frame.
        context3d_->ResetUploadCount();
        VideoFrameExternalResources resources = updater.CreateExternalResourcesFromVideoFrame(video_frame);
        EXPECT_EQ(VideoFrameExternalResources::YUV_RESOURCE, resources.type);
        EXPECT_EQ(size_t(3), resources.mailboxes.size());
        EXPECT_EQ(size_t(3), resources.release_callbacks.size());
        EXPECT_EQ(size_t(0), resources.software_resources.size());
        // Expect exactly three texture uploads, one for each plane.
        EXPECT_EQ(3, context3d_->UploadCount());

        // Allocate resources for the same frame.
        context3d_->ResetUploadCount();
        resources = updater.CreateExternalResourcesFromVideoFrame(video_frame);
        EXPECT_EQ(VideoFrameExternalResources::YUV_RESOURCE, resources.type);
        EXPECT_EQ(size_t(3), resources.mailboxes.size());
        EXPECT_EQ(size_t(3), resources.release_callbacks.size());
        // The data should be reused so expect no texture uploads.
        EXPECT_EQ(0, context3d_->UploadCount());
    }

    TEST_F(VideoResourceUpdaterTest, SoftwareFrameSoftwareCompositor)
    {
        VideoResourceUpdater updater(nullptr, resource_provider_software_.get());
        scoped_refptr<media::VideoFrame> video_frame = CreateTestYUVVideoFrame();

        VideoFrameExternalResources resources = updater.CreateExternalResourcesFromVideoFrame(video_frame);
        EXPECT_EQ(VideoFrameExternalResources::SOFTWARE_RESOURCE, resources.type);
    }

    TEST_F(VideoResourceUpdaterTest, ReuseResourceSoftwareCompositor)
    {
        VideoResourceUpdater updater(nullptr, resource_provider_software_.get());
        scoped_refptr<media::VideoFrame> video_frame = CreateTestYUVVideoFrame();
        video_frame->set_timestamp(base::TimeDelta::FromSeconds(1234));

        // Allocate the resources for a software video frame.
        shared_bitmap_manager_->ResetAllocationCount();
        VideoFrameExternalResources resources = updater.CreateExternalResourcesFromVideoFrame(video_frame);
        EXPECT_EQ(VideoFrameExternalResources::SOFTWARE_RESOURCE, resources.type);
        EXPECT_EQ(size_t(0), resources.mailboxes.size());
        EXPECT_EQ(size_t(0), resources.release_callbacks.size());
        EXPECT_EQ(size_t(1), resources.software_resources.size());
        // Expect exactly one allocated shared bitmap.
        EXPECT_EQ(1, shared_bitmap_manager_->AllocationCount());

        // Simulate the ResourceProvider releasing the resource back to the video
        // updater.
        resources.software_release_callback.Run(gpu::SyncToken(), false, nullptr);

        // Allocate resources for the same frame.
        shared_bitmap_manager_->ResetAllocationCount();
        resources = updater.CreateExternalResourcesFromVideoFrame(video_frame);
        EXPECT_EQ(VideoFrameExternalResources::SOFTWARE_RESOURCE, resources.type);
        EXPECT_EQ(size_t(0), resources.mailboxes.size());
        EXPECT_EQ(size_t(0), resources.release_callbacks.size());
        EXPECT_EQ(size_t(1), resources.software_resources.size());
        // The data should be reused so expect no new allocations.
        EXPECT_EQ(0, shared_bitmap_manager_->AllocationCount());
    }

    TEST_F(VideoResourceUpdaterTest, ReuseResourceNoDeleteSoftwareCompositor)
    {
        VideoResourceUpdater updater(nullptr, resource_provider_software_.get());
        scoped_refptr<media::VideoFrame> video_frame = CreateTestYUVVideoFrame();
        video_frame->set_timestamp(base::TimeDelta::FromSeconds(1234));

        // Allocate the resources for a software video frame.
        shared_bitmap_manager_->ResetAllocationCount();
        VideoFrameExternalResources resources = updater.CreateExternalResourcesFromVideoFrame(video_frame);
        EXPECT_EQ(VideoFrameExternalResources::SOFTWARE_RESOURCE, resources.type);
        EXPECT_EQ(size_t(0), resources.mailboxes.size());
        EXPECT_EQ(size_t(0), resources.release_callbacks.size());
        EXPECT_EQ(size_t(1), resources.software_resources.size());
        // Expect exactly one allocated shared bitmap.
        EXPECT_EQ(1, shared_bitmap_manager_->AllocationCount());

        // Allocate resources for the same frame.
        shared_bitmap_manager_->ResetAllocationCount();
        resources = updater.CreateExternalResourcesFromVideoFrame(video_frame);
        EXPECT_EQ(VideoFrameExternalResources::SOFTWARE_RESOURCE, resources.type);
        EXPECT_EQ(size_t(0), resources.mailboxes.size());
        EXPECT_EQ(size_t(0), resources.release_callbacks.size());
        EXPECT_EQ(size_t(1), resources.software_resources.size());
        // The data should be reused so expect no new allocations.
        EXPECT_EQ(0, shared_bitmap_manager_->AllocationCount());
    }

    TEST_F(VideoResourceUpdaterTest, CreateForHardwarePlanes)
    {
        VideoResourceUpdater updater(context_provider_.get(),
            resource_provider3d_.get());

        scoped_refptr<media::VideoFrame> video_frame = CreateTestRGBAHardwareVideoFrame();

        VideoFrameExternalResources resources = updater.CreateExternalResourcesFromVideoFrame(video_frame);
        EXPECT_EQ(VideoFrameExternalResources::RGBA_PREMULTIPLIED_RESOURCE,
            resources.type);
        EXPECT_EQ(1u, resources.mailboxes.size());
        EXPECT_EQ(1u, resources.release_callbacks.size());
        EXPECT_EQ(0u, resources.software_resources.size());

        video_frame = CreateTestYuvHardwareVideoFrame();

        resources = updater.CreateExternalResourcesFromVideoFrame(video_frame);
        EXPECT_EQ(VideoFrameExternalResources::YUV_RESOURCE, resources.type);
        EXPECT_EQ(3u, resources.mailboxes.size());
        EXPECT_EQ(3u, resources.release_callbacks.size());
        EXPECT_EQ(0u, resources.software_resources.size());
        EXPECT_FALSE(resources.read_lock_fences_enabled);

        video_frame = CreateTestYuvHardwareVideoFrame();
        video_frame->metadata()->SetBoolean(
            media::VideoFrameMetadata::READ_LOCK_FENCES_ENABLED, true);

        resources = updater.CreateExternalResourcesFromVideoFrame(video_frame);
        EXPECT_TRUE(resources.read_lock_fences_enabled);
    }

    TEST_F(VideoResourceUpdaterTest, CreateForHardwarePlanes_StreamTexture)
    {
        VideoResourceUpdater updater(context_provider_.get(),
            resource_provider3d_.get());
        context3d_->ResetTextureCreationCount();
        scoped_refptr<media::VideoFrame> video_frame = CreateTestStreamTextureHardwareVideoFrame(false);

        VideoFrameExternalResources resources = updater.CreateExternalResourcesFromVideoFrame(video_frame);
        EXPECT_EQ(VideoFrameExternalResources::STREAM_TEXTURE_RESOURCE,
            resources.type);
        EXPECT_EQ(1u, resources.mailboxes.size());
        EXPECT_EQ((GLenum)GL_TEXTURE_EXTERNAL_OES, resources.mailboxes[0].target());
        EXPECT_EQ(1u, resources.release_callbacks.size());
        EXPECT_EQ(0u, resources.software_resources.size());
        EXPECT_EQ(0, context3d_->TextureCreationCount());

        // A copied stream texture should return an RGBA resource in a new
        // GL_TEXTURE_2D texture.
        context3d_->ResetTextureCreationCount();
        video_frame = CreateTestStreamTextureHardwareVideoFrame(true);
        context3d_->ResetImmutableTextureCreated();
        resources = updater.CreateExternalResourcesFromVideoFrame(video_frame);
        EXPECT_EQ(VideoFrameExternalResources::RGBA_RESOURCE, resources.type);
        EXPECT_EQ(1u, resources.mailboxes.size());
        EXPECT_EQ((GLenum)GL_TEXTURE_2D, resources.mailboxes[0].target());
        EXPECT_EQ(1u, resources.release_callbacks.size());
        EXPECT_EQ(0u, resources.software_resources.size());
        EXPECT_EQ(1, context3d_->TextureCreationCount());

        // The texture copy path requires the use of CopyTextureCHROMIUM, which
        // enforces that the target texture not be immutable, as it may need
        // to alter the storage of the texture. Therefore, this test asserts
        // that an immutable texture wasn't created by glTexStorage2DEXT, when
        // that extension is supported.
        EXPECT_FALSE(context3d_->WasImmutableTextureCreated());
    }

    namespace {

        // Convert an IEEE 754 half-float to a double value
        // that we can do math on.
        double FromHalfFloat(uint16_t half_float)
        {
            if (!half_float)
                return 0.0;
            int sign = (half_float & 0x8000) ? -1 : 1;
            int exponent = (half_float >> 10) & 0x1F;
            int fraction = half_float & 0x3FF;
            if (exponent == 0) {
                return pow(2.0, -24.0) * fraction;
            } else if (exponent == 0x1F) {
                return sign * 1000000000000.0;
            } else {
                return pow(2.0, exponent - 25) * (0x400 + fraction);
            }
        }

    } // namespace

    TEST_F(VideoResourceUpdaterTest, MakeHalfFloatTest)
    {
        unsigned short integers[1 << 16];
        unsigned short half_floats[1 << 16];
        for (int bits = 9; bits <= 16; bits++) {
            std::unique_ptr<VideoResourceUpdater::HalfFloatMaker> half_float_maker;
            half_float_maker = VideoResourceUpdater::NewHalfFloatMaker(bits);
            int num_values = 1 << bits;
            for (int i = 0; i < num_values; i++)
                integers[i] = i;

            half_float_maker->MakeHalfFloats(integers, num_values, half_floats);
            // Multiplier to converting integers to 0.0..1.0 range.
            double multiplier = 1.0 / (num_values - 1);

            for (int i = 0; i < num_values; i++) {
                // This value is in range 0..1
                float value = integers[i] * multiplier;
                // Reverse the effect of offset and multiplier to get the expected
                // output value from the half-float converter.
                float expected_value = value / half_float_maker->Multiplier() + half_float_maker->Offset();
                EXPECT_EQ(integers[i], i);

                // We expect the result to be within +/- one least-significant bit.
                // Within the range we care about, half-floats values and
                // their representation both sort in the same order, so we
                // can just add one to get the next bigger half-float.
                float expected_precision = FromHalfFloat(half_floats[i] + 1) - FromHalfFloat(half_floats[i]);
                EXPECT_NEAR(FromHalfFloat(half_floats[i]), expected_value,
                    expected_precision)
                    << "i = " << i << " bits = " << bits;
            }
        }
    }

} // namespace
} // namespace cc
